#ifndef MOCK_H
#define MOCK_H

#include <deque>
#include <iostream>
#include <string>

using namespace std;

struct UnexpectedCallException {
	string methodName;
	string file;
	long line;
};
std::ostream& operator << (std::ostream& out, const UnexpectedCallException ex);
bool operator == (const UnexpectedCallException e1, UnexpectedCallException e2);

class Mock {
private:
	deque<string> expected;
//	deque<Expectation> expectations;
protected:
	void actual(string methodName);
public:
	static bool recordMode;

	void expect(string methodName);
	void verify();
};

class Method {
protected:
	deque<string> expected;

	void actual(string methodName, string file, long line) {
		UnexpectedCallException ex;
		ex.methodName = methodName;
		ex.file = file;
		ex.line = line;
		if (expected.empty()) {
			throw ex;
		}
		if (methodName == expected.at(0)) {
			expected.pop_front();
		}
		else {
			// unexpected call
			throw ex;
		}
	}
	void expect(string methodName) {
		expected.push_front(methodName);
	}
public:
	void verify() {
		if (expected.size() != 0) {
			throw 0;
		}
	}
	void call(string methodName, string file, long line) {
		if (Mock::recordMode) {
			expect(methodName);
		}
		else {
			actual(methodName, file, line);
		}
	}
};

template <typename ParamType>
class MethodWithOneParam : public Method {
private:
	deque<ParamType> param1;
public:
	void verify() {
		if (expected.size() != 0) {
			throw 0;
		}
	}
	void call(string methodName, ParamType param, string file, long line) {
		if (Mock::recordMode) {
			expect(methodName, param);
		}
		else {
			actual(methodName, param, file, line);
		}
	}
private:
	void expect(string methodName, ParamType param) {
		expected.push_back(methodName);
		param1.push_back(param);
	}
	void actual(string methodName, ParamType param, string file, long line) {
		UnexpectedCallException ex;
		ex.methodName = methodName;
		ex.file = file;
		ex.line = line;
		if (expected.empty()) {
			throw ex;
		}
		if (methodName == expected.at(0) && param == param1.at(0)) {
			expected.pop_front();
		}
		else {
			// unexpected call
			throw ex;
		}
	}
};







template <typename ParamType>
class MethodParam {
private:
	deque<ParamType> expected;
public:
	void expect(ParamType param) {
		expected.push_back(param);
	}
	void actual(ParamType param) {
		if (expected.empty()) {
			// unexpected call
			throw 0;
		}
		if (param == expected.front()) {
			expected.pop_front();
		}
		else {
			// unexpected call
			throw 0;
		}
	}
	void verify() {
		for (unsigned int i=0; i<expected.size(); i++) {
			// unsatisfied expected call
			throw 0;
		}
	};
};

class Expectation {
public:
	string methodName;
};
#endif